﻿using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Threading;
using System.Web;
using System.Web.Caching;
using System.Xml.Serialization;
using TSharp.Core;
using TSharp.Core.Caching;

namespace TSharp.Core.Util.Config
{
    /// <summary>
    /// 序列化基类  by Tjb 2007/06 
    /// </summary>
    /// T 类型不能创建多态的多个实例
    /// <para>
    /// 优化和修正，增加配置文件备份 2008/9/18
    /// </para>
    /// <para>
    /// 修改：获取实例时加锁
    /// </para>
    /// <para>
    /// 修改： by tjb @ 2008/10/24
    /// SaveAs(T Tobj, string FileFullName, bool BackupOnError) 方法增加线程锁定。防止多线程冲突
    /// </para>
    /// <para>
    /// 修改： by tjb @ 2008/12/16
    /// 优化线程锁，以及优化缓存key的获取方式，使得typeof(T)运算只运行一次
    /// </para>
    /// <typeparam name="T">T 必须有无参数的构造函数</typeparam>
    [Serializable]
    public abstract class ConfigBase<T> where T : ConfigBase<T>, new()
    {
        #region Exceptions

        [NonSerialized]
        private Exception __ConfigException;

        /// <summary>
        /// 读取或保存时发生的最后一个错误
        /// </summary>
        [XmlIgnore, SoapIgnore]
        public Exception LastException
        {
            get { return __ConfigException; }
            private set
            {
                if (value != null)
                {
                    __ConfigException = value;
                    OnError(__ConfigException);
                }
            }
        }

        /// <summary>
        /// 发生错误时处理方法
        /// </summary>
        /// <param name="ex"></param>
        protected virtual void OnError(Exception ex)
        {
        }

        #endregion

        #region 序列化类型  可重写

        private FormatType _CurrentFormatter = FormatType.Xml;

        /// <summary>
        /// 序列化的方式
        /// </summary>      
        [XmlIgnore]
        protected virtual FormatType CurrentFormatter
        {
            get { return _CurrentFormatter; }
            set { _CurrentFormatter = value; }
        }

        #endregion

        #region 获取单个实例

        /// <summary>
        /// 
        /// </summary>
        [NonSerialized]
        private static readonly Type _CurType = typeof(T);

        /// <summary>
        /// 获取Cache中缓存T的key
        /// </summary>
        public static readonly string CacheKEY = "_cfg$tjb$" + _CurType.FullName;

        [NonSerialized]
        private static readonly object _Locker = _CurType; //new object();// Guid.NewGuid();

        /// <summary>
        /// Updates the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="reason">The reason.</param>
        /// <param name="expensiveObject">The expensive object.</param>
        /// <param name="dependency">The dependency.</param>
        /// <param name="absoluteExpiration">The absolute expiration.</param>
        /// <param name="slidingExpiration">The sliding expiration.</param>
        public static void Update(string key, CacheItemUpdateReason reason, out object expensiveObject, out System.Web.Caching.CacheDependency dependency, out System.DateTime absoluteExpiration, out System.TimeSpan slidingExpiration)
        {
            expensiveObject = null;
            dependency = null;
            absoluteExpiration = DateTime.Now;
            slidingExpiration = Cache.NoSlidingExpiration;
        }

        /// <summary>
        /// 从缓存（缓存KEY为T的命空间加类名）获取当前类的实例，如果缓存不存在则读取文件(ConfigFileFullName)， 并缓存。 
        /// </summary>
        public static T Instance
        {
            get
            {
                var c = CacheUtil.Get<T>(CacheKEY);
                if (c == null)
                {
                    //这中方式无异于不加锁
                    //object Monitor = new object();
                    //lock (Monitor)
                    using (var l = new Lock(_Locker, 5000))
                    {
                        c = CacheUtil.Get<T>(CacheKEY);
                        if (c == null)
                        {
                            c = new T();
                            if (File.Exists(c.___file))
                                c = c.Read();
#if DEBUG
                            CacheUtil.AddOrUpdate(CacheKEY, k => c, (k, v) => c, TimeSpan.FromDays(7), new CacheDependency(c.ConfigFileFullName), null);

                            //CacheUtil.SetSlidingExpiration(CacheKEY, c, c.ConfigFileFullName, 7,
                            //                               CacheItemPriority.NotRemovable, OnCacheRemove);
#else
                            CacheUtil.AddOrUpdate(CacheKEY, k => c, (k, v) => c, TimeSpan.FromDays(7), new CacheDependency(c.ConfigFileFullName), null);
 
#endif
                        }
                    }
                }
                return c;
            }
        }

        #endregion

        #region Caching

#if DEBUG
        private static void OnCacheRemove(string key, object obj, CacheItemRemovedReason re)
        {
            Debug.WriteLine(key + " 缓存被移除，原因：" + re);
        }
#endif

        /// <summary>
        /// 从ConfigFileName指定文件以CurrentSerializeType读取实例，如果存在缓存则移除并重新进行缓存
        /// 如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <returns></returns>
        protected T ResetCacheAndRead()
        {
            ResetCache();
            return Read();
        }

        /// <summary>
        /// 移除CacheKEY 缓存
        /// </summary>
        public void ResetCache()
        {
            HttpRuntime.Cache.Remove(CacheKEY);
        }

        #endregion

        #region 获取

        /// <summary>
        /// 读取缓存中的实例如果不存在则
        /// 从ConfigFileName指定文件以CurrentSerializeType读取实例
        /// 如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <returns></returns>
        protected virtual T Read()
        {
            return Read(ConfigFileFullName, true, CurrentFormatter);
        }

        /// <summary>
        ///  从ConfigFileName指定文件以CurrentSerializeType读取实例
        ///  如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <param name="configFileFullName">缓存文件绝对路径</param>
        /// <param name="serializeType"></param>
        /// <param name="BackupOnError">读取错误时是否备份</param>
        /// <returns></returns>
        protected T Read(string configFileFullName, bool BackupOnError, FormatType serializeType)
        {
            var t = CacheUtil.Get<T>(CacheKEY);
            if (t != null)
                return t;
            using (var l = new Lock(_Locker, 10000))
            {
                var fi = new FileInfo(configFileFullName);
                if (fi.Exists)
                {
                    FileStream fs = null;
                    try
                    {
                        #region  open the stream...

                        // open the stream...
                        fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                        IFormatter formatter = null;
                        switch (serializeType)
                        {
                            case FormatType.Binary:
                                formatter = new BinaryFormatter();
                                t = formatter.Deserialize(fs) as T;
                                break;
                            case FormatType.SoapXml:
                                formatter = new SoapFormatter();
                                t = formatter.Deserialize(fs) as T;
                                break;
                            case FormatType.Xml:
                                var serializer = new XmlSerializer(typeof(T));
                                t = serializer.Deserialize(fs) as T;
                                break;
                            default:
                                break;
                        }

                        #endregion

                        if (fs != null)
                            fs.Close();
                    }
                    catch (Exception ex)
                    {
                        if (fs != null)
                            fs.Close();

                        #region 读取失败 进行备份 防止以后使用默认配置覆盖当前数据

                        if (BackupOnError)
                            if (fi.Exists)
                            {
                                string BackupFilePath = fi.FullName + ".{Read" + DateTime.Now.ToString("yyMMddHHssfff") +
                                                        "}.bak";
                                try
                                {
                                    fi.CopyTo(BackupFilePath, true);
                                    // t.Save(true);
                                }
                                catch (Exception ioex)
                                {
                                    t.LastException = new Exception("读取配置文件出错后，对配置文件进行备份发生错误！", ioex);
                                }
                            }

                        #endregion

                        t = CustomizingInit();
                        t.LastException = new Exception("读取配置文件时发生错误！将返回CustomizingInit()方法的实例，默认为 new T()！", ex);
                    }
                    finally
                    {
                    }
                }
                else
                {
                    #region 配置文件不存在

                    t = CustomizingInit();
                    //不存在的同一文件不能读多次
                    //防止在CustomizingInit()方法中调用Read(string configFileName, SerializeType serializeType) 造成死循环
                    string ProtectedKEY = "Config_InitProtectedKEY" + Thread.CurrentThread.ManagedThreadId + fi.FullName;
                    CacheUtil.AddOrUpdate<bool>(ProtectedKEY,
                        key => true,
                        (key, oldval) =>
                        {
                            LastException = new Exception("'" + fi.FullName + "' 文件不存在！读取过于频繁。导致此保护异常发生。");
                            return false;
                        },
                        TimeSpan.FromMinutes(0.02), null, null);

                    t.LastException = new Exception("配置文件：'" + fi.FullName + "' 不存在！默认返回CustomizingInit()方法返回的实例。");

                    #endregion
                }
                if (t == null)
                    t.LastException =
                        new Exception("发生错误，请检查初始化配置方法 CustomizingInit() 未返回的配置实例不能为空，或者系统中存在缓存 key ：" + CacheKEY);
            }
            return t;
        }

        #endregion

        #region 保存

        /// <summary>
        /// 将当前实例进行保存
        /// </summary>
        /// <param name="BackupOnError">发生错误时是否备份</param>
        /// <returns></returns>
        public bool Save(bool BackupOnError)
        {
            if (Current == null)
            {
                LastException = new Exception(_CurType.FullName + " 未正确实现 Current；");
                throw LastException;
            }
            return Save(Current, BackupOnError);
        }

        /// <summary>
        /// 将当前实例进行保存
        /// 发生错误时将备份配置文件
        /// </summary>
        /// <returns></returns>
        public virtual bool Save()
        {
            return Save(true);
        }

        /// <summary>
        /// 将当前实例保存到T.ConfigFileName指定的配置文件
        /// </summary>
        /// <param name="Tobj"></param>
        /// <param name="BackupOnError"></param>
        /// <returns></returns>      
        public bool Save(T Tobj, bool BackupOnError)
        {
            return SaveAs(Tobj, ConfigFileFullName, BackupOnError);
        }

        /// <summary>
        /// 将一个实例对象保存到FileFullName指定的配置文件
        /// </summary>
        /// <param name="FileFullName"></param>
        /// <param name="Tobj"></param>
        /// <param name="BackupOnError"></param>
        /// <returns></returns>      
        protected bool SaveAs(T Tobj, string FileFullName, bool BackupOnError)
        {
            using (var l = new Lock(_Locker, 10000))
            {
                FileStream fs = null;
                var fi = new FileInfo(FileFullName);
                string BackupFilePath = null;
                if (BackupOnError)
                {
                    #region backup

                    try
                    {
                        //如果存在先备份
                        if (fi.Exists)
                        {
                            BackupFilePath = fi.FullName + ".{Save" + DateTime.Now.ToString("yyMMddHHssfff}")
                                /* + Guid.NewGuid().ToString("B") */+ ".bak";
                            fi.CopyTo(BackupFilePath, true);
                        }
                        else if (!fi.Directory.Exists)
                        {
                            //目录不存在创建目录
                            fi.Directory.Create();
                        }
                    }
                    catch (Exception ex)
                    {
                        Tobj.LastException = new Exception("备份配置文件时发生错误！", ex);
                        return false;
                    }

                    #endregion
                }
                try
                {
                    if (fi.Exists)
                        fi.Delete();

                    #region  serialize it...

                    fs = new FileStream(fi.FullName, FileMode.Create, FileAccess.Write, FileShare.Read);
                    IFormatter formatter = null;
                    switch (CurrentFormatter)
                    {
                        case FormatType.Binary:
                            formatter = new BinaryFormatter();
                            formatter.Serialize(fs, Tobj);
                            break;
                        case FormatType.SoapXml:
                            formatter = new SoapFormatter();
                            formatter.Serialize(fs, Tobj);
                            break;
                        case FormatType.Xml:
                            var serializer = new XmlSerializer(_CurType);
                            serializer.Serialize(fs, Tobj);
                            break;
                        default:
                            break;
                    }

                    #endregion

                    #region 成功后进行缓存

#if DEBUG
                    CacheUtil.AddOrUpdate(CacheKEY, k => Tobj, (k, v) => Tobj, TimeSpan.FromDays(7), new CacheDependency(Tobj.ConfigFileFullName), null);
                    //CacheUtil.SetSlidingExpiration(CacheKEY, Tobj, Tobj.ConfigFileFullName, 7,
                    //                               CacheItemPriority.NotRemovable, OnCacheRemove);
#else
                    CacheUtil.AddOrUpdate(CacheKEY, k => Tobj, (k, v) => Tobj, TimeSpan.FromDays(7), new CacheDependency(Tobj.ConfigFileFullName), null);
                   
#endif
                    //SetCache(Tobj);

                    #endregion

                    #region 成功则删除当前备份

                    if (BackupOnError && !string.IsNullOrEmpty(BackupFilePath))
                        try
                        {
                            File.Delete(BackupFilePath);
                        }
                        catch (Exception ex)
                        {
                            Tobj.LastException = new Exception("删除备份文件时发生错误！", ex);
                        }

                    #endregion

                    return true;
                }
                catch (UnauthorizedAccessException ioex)
                {
                    Tobj.LastException =
                        new Exception(
                            string.Format("保存配置文件时发生IO错误！[TheadID:{0},ThreadName:{1}]",
                                          Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name), ioex);
                    return false;
                }
                catch (Exception ex)
                {
                    Tobj.LastException =
                        new Exception(
                            string.Format("保存配置文件时发生错误！[TheadID:{0},ThreadName:{1}]",
                                          Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name), ex);
                    return false;
                }
                finally
                {
                    if (fs != null)
                        fs.Close();
                }
            }
        }

        #endregion

        #region 删除配置文件

        /// <summary>
        /// 删除配置文件
        /// </summary>
        /// <returns></returns>
        public bool Delete()
        {
            return Delete(ConfigFileFullName);
        }

        /// <summary>
        /// 删除指定的配置文件 .文件不存在不引发异常。并返回true
        /// </summary>
        /// <param name="FileFullName"></param>
        /// <returns></returns>
        public bool Delete(string FileFullName)
        {
            try
            {
                if (File.Exists(FileFullName))
                    File.Delete(FileFullName);
                return true;
            }
            catch (Exception ex)
            {
                LastException = ex;
                return false;
            }
        }

        #endregion

        #region  抽象成员

        /// <summary>
        /// 返回派生类的当前实例
        /// <para>protected override T Current</para>
        /// <para>{</para>
        /// <para>  get</para>
        /// <para>  {</para>
        /// <para>      return this;</para>
        /// <para>  }</para>
        /// <para>}</para>        
        /// <para>
        /// by tangjingbo at 2009-2-24 12:21
        /// </para>
        /// </summary>
        /// <value>T</value>
        protected abstract T Current { get; }

        /// <summary>
        /// 自定义初始化配置类
        /// 获取 T 的配置实例时，如果配置文件不存在或者读取失败时返回此实例
        /// 重写中一般读取备份的配置文件，必须保证备份配置文件存在，否则发生异常。
        /// 默认返回    new T();
        /// <para>
        /// by tangjingbo at 2009-2-24 12:22
        /// </para>
        /// </summary>
        /// <returns></returns>
        protected virtual T CustomizingInit()
        {
            return Current;
        }

        /// <summary>
        /// 配置文件名
        /// <c>配置文件名</c>
        /// <example>
        /// 配置文件名
        /// </example>
        /// </summary>
        [NonSerialized]
        private string ___file = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                                              @"App_Data\" + _CurType.Name + ".config");

        /// <summary>
        /// 配置文件全路径名
        /// 默认：winform 在应用程序目录；web应用程序默认bin目录。
        /// </summary>
        [XmlIgnore, SoapIgnore]
        public virtual string ConfigFileFullName
        {
            get { return ___file; }
            set { ___file = value; }
        }

        #endregion
    }
}